﻿using System;
using System.Collections.Generic;

namespace SharpTag.ID3
{
    /// <summary>
    /// Concrete implementation of the <see cref="IID3v2Tag"/> interface.
    /// </summary>
    public class ID3V2Tag : IID3v2Tag
    {
        private AbstractFrameFactory m_frameFactory = new ID3FrameFactory();

        /// <summary>
        /// Class constructor.
        /// </summary>
        public ID3V2Tag()
        {
            IsDirty = false;
        }

        /// <summary>
        /// Class constructor
        /// </summary>
        /// <param name="offset">The offset of the tag within the file.</param>
        public ID3V2Tag(long offset)
        {
            Offset = offset;
            IsDirty = false;
        }

        /// <summary>
        /// Gets the <see cref="ITagHeader"/> of the Tag.
        /// </summary>
        public ITagHeader Header { get; private set; }

        /// <summary>
        /// Gets the <see cref="ITagExtendedHeader"/> of the Tag.
        /// </summary>
        public ITagExtendedHeader ExtendedHeader { get; private set; }

        /// <summary>
        /// Gets the amount of Padding in the Tag.
        /// </summary>
        public long Padding { get; private set; }

        /// <summary>
        /// Gets the <see cref="ITagFooter"/> of the Tag.
        /// </summary>
        public ITagFooter Footer { get; private set; }

        /// <summary>
        /// Gets the list of <see cref="IFrame"/> objects found in the Tag.
        /// </summary>
        public List<IFrame> Frames { get; private set; }

        /// <summary>
        /// Adds an <see cref="IFrame"/> object to the Frames collection.
        /// </summary>
        /// <param name="frame"></param>
        public void AddFrame(IFrame frame)
        {
            if (!Frames.Contains(frame))
                Frames.Add(frame);
        }

        /// <summary>
        /// Removes an <see cref="IFrame"/> object from the Frames collection.
        /// </summary>
        /// <param name="frame"></param>
        public void RemoveFrame(IFrame frame)
        {
            if (Frames.Contains(frame))
                Frames.Remove(frame);
        }

        /// <summary>
        /// Gets the Name of the Tag.
        /// </summary>
        public string Name { get; private set; }

        /// <summary>
        /// Gets the Description of the Tag.
        /// </summary>
        public string Description { get; private set; }

        /// <summary>
        /// Gets the Offset of the Tag.
        /// </summary>
        public long Offset { get; private set; }

        /// <summary>
        /// Gets the Version of the Tag.
        /// </summary>
        public double Version { get; private set; }

        /// <summary>
        /// Gets the Album found in the Tag.
        /// </summary>
        public string Album { get; private set; }

        /// <summary>
        /// Gets the AlbumTrack found in the Tag.
        /// </summary>
        public int AlbumTrack { get; private set; }

        /// <summary>
        /// Gets the Artist found in the Tag.
        /// </summary>
        public string Artist { get; private set; }

        /// <summary>
        /// Gets the Comment found in the Tag.
        /// </summary>
        public string Comment { get; private set; }

        /// <summary>
        /// Gets the Title found in the Tag.
        /// </summary>
        public string Title { get; private set; }

        /// <summary>
        /// Gets the Year found in the Tag.
        /// </summary>
        public int Year { get; private set; }

        /// <summary>
        /// Gets the Genre found in the Tag.
        /// </summary>
        public string Genre { get; private set; }

        /// <summary>
        /// Gets whether or not the Tag has been modified.
        /// </summary>
        public bool IsDirty { get; private set; }

        /// <summary>
        /// Sets the Album of the Tag.
        /// </summary>
        /// <param name="albumName">The Album name.</param>
        /// <remarks>Album can be set to any valid string, but when written back to the file it will be made to a max of 30 characters.</remarks>
        public void SetAlbum(string albumName)
        {
            Album = albumName;
            IsDirty = true;
        }

        /// <summary>
        /// Sets the Album Track of the Tag.
        /// </summary>
        /// <param name="albumTrack">The Track number.</param>
        public void SetAlbumTrack(int albumTrack)
        {
            AlbumTrack = albumTrack;
            IsDirty = true;
        }

        /// <summary>
        /// Sets the Artist name of the Tag.
        /// </summary>
        /// <param name="artistName">The Artist name.</param>
        public void SetArtist(string artistName)
        {
            Artist = artistName;
            IsDirty = true;
        }

        /// <summary>
        /// Sets the Comment of the Tag.
        /// </summary>
        /// <param name="comment">The Comment string.</param>
        public void SetComment(string comment)
        {
            Comment = comment;
            IsDirty = true;
        }

        /// <summary>
        /// Sets the Title of the Tag.
        /// </summary>
        /// <param name="title">The Title string.</param>
        public void SetTitle(string title)
        {
            Title = title;
            IsDirty = true;
        }

        /// <summary>
        /// Sets the Year of the Tag.
        /// </summary>
        /// <param name="year">The Year integer.</param>
        public void SetYear(int year)
        {
            Year = year;
            IsDirty = true;
        }

        /// <summary>
        /// Sets the Genre of the Tag.
        /// </summary>
        /// <param name="genre">The Genre string.</param>
        public void SetGenre(string genre)
        {
            Genre = genre;
            IsDirty = true;
        }

        /// <summary>
        /// Parses the raw tag data into the appropriate properties.
        /// </summary>
        /// <param name="tagBytes">A raw byte array containing the tag data.</param>
        public void ParseTagData(byte[] tagBytes)
        {
            var headerBytes = new byte[10];
            Array.Copy(tagBytes, 0, headerBytes, 0, 10);
            Header = new ID3V2TagHeader();
            Header.ParseHeaderData(headerBytes);
            var frameBytesStartPoint = 10;

            // TODO: Add in logic and parsing of ExtendedHeader and Footer

            if (Header.Flags != null)
            {
                if (!Header.Flags.Contains(TagHeaderFlag.ExtendedHeader))
                    ExtendedHeader = null;
                else

                if (!Header.Flags.Contains(TagHeaderFlag.FooterPresent))
                    Footer = null;
            }

            var frameBytesSize = (int)Header.Size;
            if(ExtendedHeader != null)
            {
                frameBytesStartPoint += 10;
                frameBytesSize -= 10;
            }

            var frameBytes = new byte[frameBytesSize];
            Array.Copy(tagBytes, frameBytesStartPoint, frameBytes, 0, frameBytesSize);
            Frames = m_frameFactory.ParseID3TagFrames(frameBytes);
            var ver = 0;
            var year = 0;
            Genre genre;

            Name = "ID3v2";
            Version = Header.Version;
            AlbumTrack = int.TryParse(GetFrameValue("TRCK", "Information"), out ver) == true ? ver : 0;
            Album = GetFrameValue("TALB", "Information");
            Artist = GetFrameValue("TPE1", "Information");
            Comment = GetFrameValue("COMM", "Text");
            Year = int.TryParse(GetFrameValue("TYER", "Information"), out year) == true ? year : 0;
            Genre = Enum.TryParse<Genre>(GetFrameValue("TCON", "Information"), out genre) == true ? genre.ToString() : GetFrameValue("TCON", "Information");
            Title = GetFrameValue("TIT2", "Information");
        }

        /// <summary>
        /// Converts the Frame Header data into a byte array to be written out to the physical file.
        /// </summary>
        /// <returns></returns>
        public byte[] PrepareBytes()
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Gets a string value of a specified field in a specified frame.
        /// </summary>
        /// <param name="frameName">The name of the target frame.</param>
        /// <param name="fieldName">The name of the target field.</param>
        /// <returns>A string representation of the target frame and field.</returns>
        private string GetFrameValue(string frameName, string fieldName)
        {
            foreach (var frame in Frames)
            {
                if(frame.Header.Name == frameName)
                {
                    foreach (var field in frame.Data)
                    {
                        if (field.Key == fieldName)
                            return field.Value.ToString();
                    }
                }
            }

            return string.Empty;
        }
    }
}
